﻿namespace ImageDupFinder
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using ImageDupFinder.i18n;
    using ImageDupFinder.PluginCore;
    using ImageDupFinder.ProgramCore;

    /// <summary>
    /// Main form for ImageDupFinder application.
    /// </summary>
    public partial class MainForm : Form, IPluginHost
    {
        /// <summary>
        /// basic set of features ("histogram" etc.)
        /// </summary>
        private IFeatureCollection basicFeatures;

        /// <summary>
        /// manages plugin related tasks
        /// </summary>
        private PluginManager pluginManager;

        /// <summary>
        /// manages images related tasks
        /// </summary>
        private ImageManager imageManager;

        /// <summary>
        /// manages files related tasks
        /// </summary>
        private FileManager fileManager;

        /// <summary>
        /// inernationalized strings provider
        /// </summary>
        private I18nManager i18nManager;

        /// <summary>
        /// cache for pictureboxes with thumbnails
        /// </summary>
        private Dictionary<string, PictureBox> pictureboxes;

        /// <summary>
        /// Thumbnail image tooltip
        /// </summary>
        private ToolTip thumbTooltip;

        /// <summary>
        /// currently selected file
        /// </summary>
        private string curFile = String.Empty;

        private ITaskbarList4 win7TaskBar =null;

        #region  i18n private fields and initialization
        /// <summary>
        /// "Image done" text
        /// </summary>
        private string i18n_ImageDone;

        /// <summary>
        /// "Caltulating similarity" text
        /// </summary>
        private string i18n_CalculatingSim;

        /// <summary>
        /// "files added" text
        /// </summary>
        private string i18n_FilesAdded;

        /// <summary>
        /// "size" text
        /// </summary>
        private string i18n_Size;

        /// <summary>
        /// "Identical" text
        /// </summary>
        private string i18n_Identical;
        #endregion

#if DEBUG
        /// <summary>
        /// Debug related field.
        /// </summary>
        private DateTime startTime;
#endif

        /// <summary>
        /// Initializes a new instance of the MainForm class.
        /// </summary>
        public MainForm()
        {
            // init fields
            pictureboxes = new Dictionary<string, PictureBox>();
            basicFeatures = new DefaultFeaturesSet();

            // init WinForms components
            InitializeComponent();

            // init i18n
            InitializeI18n();

            // load features from plugins, default plugins location is ".\Plugins\" folder
            pluginManager = new PluginManager(Application.StartupPath + "\\Plugins\\", this);

            // set Application icon
            this.Icon = ImageDupFinder.Properties.Resources.AppIcon;

            // init image manager
            this.imageManager = new ImageManager();
            this.imageManager.AllTasksCompleted += new EventHandler(ImageManager_AllTasksCompleted);
            this.imageManager.LoaderReportProgress += new EventHandler<ProgressEventArgs>(ImageManager_LoaderReportProgress);
            this.imageManager.CalculatorReportProgress += new EventHandler<ProgressEventArgs>(ImageManager_CalculatorReportProgress);
            this.imageManager.DuplicatesFound += new EventHandler<ImgSearchEventArgs>(ImageManager_DuplicatesFound);

            // init file manager
            this.fileManager = new FileManager(this.imageManager);
            this.fileManager.ThumbnailImageLoaded += new EventHandler<ThumbLoaderItem>(FileManager_ThumbnailLoaded);
            this.fileManager.FilesAdded += new EventHandler(FileManager_FilesAdded);
            this.fileManager.FileRemoved += new EventHandler<FileEventArgs>(FileManager_FileRemoved);

            // init thumbnail tooltip
            this.thumbTooltip = new ToolTip();

            // fill list box with features
            FillFeatureListBox();
            listBoxFeatures.SetItemChecked(0, true);
            imageManager.SetSelectedFeatures(new string[] { basicFeatures.FeatureList[0] });

            this.win7TaskBar = Win7TaskBarSupport.TaskbarList;
        }

        #region IPluginHost members
        /// <summary>
        /// Registers new plugin.
        /// </summary>
        /// <param name="ipi">plugin to register</param>
        /// <returns>true if plugin was successfully registered</returns>
        public bool Register(IPlugin ipi)
        {
            ToolStripMenuItem i = new ToolStripMenuItem(ipi.Name);
            i.Name = "pluginMenuItem_" + ipi.Name;
            i.ToolTipText = ipi.Description;
            i.AutoToolTip = true;

            // uncheck
            i.Checked = false;
            ipi.Enabled = false;

            // set click behavior
            i.CheckOnClick = true;
            i.Click += new EventHandler(Plugin_Click);

            // set plugin options
            if (ipi.HasOptions)
            {
                ToolStripMenuItem options = new ToolStripMenuItem(i18nManager.GetString("MainForm_PLUGIN_OPTIONS"));
                options.Name = i.Name + "_PluginOptions";
                options.Click += new EventHandler(PluginOptions_Click);
                i.DropDownItems.Add(options);
            }

            // add plugin
            pluginsMenuItem.DropDownItems.Add(i);
            return true;
        }
        #endregion

        #region utility functions
        /// <summary>
        /// Fill features listbox
        /// </summary>
        private void FillFeatureListBox()
        {
            imageManager.FeaturesCollections.Clear();

            // add basic features
            bool check = false;
            foreach (ToolStripMenuItem t in pluginsMenuItem.DropDownItems)
            {
                check = check | t.Checked;
            }

            if (!check)
            {
                pluginsMenuItem_Default.Checked = true;
            }

            if (pluginsMenuItem_Default.Checked || !check)
            {
                imageManager.FeaturesCollections.Add(basicFeatures);
            }

            // add plugins features
            imageManager.FeaturesCollections.AddRange(pluginManager.EnabledFeatures);

            listBoxFeatures.Items.Clear();
            foreach (IFeatureCollection c in imageManager.FeaturesCollections)
            {
                listBoxFeatures.Items.AddRange(c.FeatureList);
            }
        }

        /// <summary>
        /// Called when user change enabled plugin list
        /// </summary>
        private void PluginListChanged()
        {
            fileManager.Clear();

            // reassign features
            FillFeatureListBox();
            imageManager.SetSelectedFeatures(null);

            // reset ui
            trackBarSimilarity.Enabled = false;
            buttonPauseRestore.Visible = false;
            buttonPauseRestore.SetToggleState(false);
        }

        /// <summary>
        /// Removes file from displayed results
        /// </summary>
        /// <param name="file">file to remove</param>
        private void RemoveFromDisplay(string file)
        {
            foreach (Control panel in MainPanel.Controls)
                foreach (Control pb in panel.Controls)
                {
                    ThumbnailPictureBox thpb = pb as ThumbnailPictureBox;
                    if (thpb != null && thpb.ImageData.File == file)
                    {
                        MainPanel.SuspendLayout();
                        pb.Dispose();
                        panel.Controls.Remove(pb);
                        if (panel.Controls.Count < 2)
                        {
                            MainPanel.Controls.Remove(panel);
                        }

                        pictureboxes.Remove(file);

                        MainPanel.ResumeLayout();
                        MainPanel.InvokeNestedLazyLoad();
                        return;
                    }
                }
        }

        /// <summary>
        /// Displays result of finding duplicate images (inside main panel).
        /// </summary>
        /// <param name="list">Duplicate search result</param>
        private void DisplayResult(List<List<ImageData>> list)
        {
            MainPanel.SuspendLayout();
            MainPanel.Controls.Clear();

            Control[] panels = new Control[list.Count];
            for (int i = 0; i < list.Count; i++)
            {
                LazyPanel imgPanel = new LazyPanel(list[i]);
                imgPanel.Height = 100;
                imgPanel.AutoScroll = true;
                imgPanel.Dock = DockStyle.Top;
                imgPanel.LazyLoadCallback += new EventHandler(DisplayResult_LazyLoadCallback);

                panels[i] = imgPanel;
            }

            MainPanel.Controls.AddRange(panels);
            MainPanel.ResumeLayout();
            MainPanel.InvokeNestedLazyLoad();
        }

        /// <summary>
        /// Displays duplicate search result using lazy loading.
        /// </summary>
        /// <param name="sender">sender object</param>
        /// <param name="e">event args</param>
        private void DisplayResult_LazyLoadCallback(object sender, EventArgs e)
        {
            LazyPanel imgPanel = sender as LazyPanel;

            // create thumbnail pictureboxes
            List<ImageData> data = (List<ImageData>)imgPanel.LazyData;
            PictureBox[] boxes = data.ConvertAll(img => GetPictureBox(img)).ToArray();

            imgPanel.SuspendLayout();
            imgPanel.Controls.AddRange(boxes);
            imgPanel.ResumeLayout();

            // invoke initial lazy load on all pictureboxes
            imgPanel.InvokeNestedLazyLoad();
        }

        /// <summary>
        /// Displays image count
        /// </summary>
        private void DisplayImageCount()
        {
            msgBoxCalc.Text = String.Format(i18n_ImageDone, imageManager.Count);
        }

        /// <summary>
        /// Creates and caches picture box.   
        /// </summary>
        /// <param name="image">Image data to associate with picture box</param>
        /// <returns>picturebox associated with provided image</returns>
        private PictureBox GetPictureBox(ImageData image)
        {
            if (!pictureboxes.ContainsKey(image.File))
            {
                ThumbnailPictureBox pb = new ThumbnailPictureBox(image);
                pb.Image = Properties.Resources.hourglass;
                pb.SizeMode = PictureBoxSizeMode.Zoom;
                pb.Height = ThumbLoader.DefaultThumbnailHeight + 7;
                pb.Width = ThumbLoader.DefaultThumbnailWidth + 7;
                pb.Dock = DockStyle.Left;
                pb.ContextMenuStrip = contextMenuStripImages;
                pb.MouseDown += new MouseEventHandler(Picturebox_MouseClick);
                pb.MouseEnter += new EventHandler(Picturebox_MouseEnter);
                pb.LazyLoadCallback += new EventHandler(Picturebox_LazyLoadCallback);

                pictureboxes.Add(image.File, pb);
                return pb;
            }
            else
            {
                return pictureboxes[image.File];
            }
        }

        /// <summary>
        /// Callback function for ThumbnailPictureBox lazy loading.
        /// </summary>
        /// <param name="sender">sender object</param>
        /// <param name="e">event args</param>
        private void Picturebox_LazyLoadCallback(object sender, EventArgs e)
        {
            ThumbnailPictureBox pb = sender as ThumbnailPictureBox;
            fileManager.LoadThumbnailAsync(new ThumbLoaderItem(pb.ImageData.File, pb));
        }

        #endregion

        #region event handlers

        /// <summary>
        /// Handles progress changes reported by image manager's similarity calculations.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void ImageManager_CalculatorReportProgress(object sender, ProgressEventArgs e)
        {
            calcProgressBar.Value = e.Progress;
            DisplayImageCount();
            if (Win7TaskBarSupport.IsSupported)
            {
                win7TaskBar.SetProgressValue(this.Handle, (ulong)calcProgressBar.Value, (ulong)calcProgressBar.Maximum);
            }
        }

        /// <summary>
        /// Handles progress changes reported by image manager's image loading process.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void ImageManager_LoaderReportProgress(object sender, ProgressEventArgs e)
        {
            loaderProgressBar.Value = e.Progress;
        }

        /// <summary>
        /// Handles "all tasks completed" event reported by image manager.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void ImageManager_AllTasksCompleted(object sender, EventArgs e)
        {
#if DEBUG
            Console.WriteLine("time : " + (DateTime.Now - startTime));
#endif
            buttonPauseRestore.Visible = false;
            trackBarSimilarity.Enabled = true;
            listBoxFeatures.Enabled = true;

            // msgBoxLoader.Text = String.Empty;
            DisplayImageCount();

            // find duplicate images
            TrackBarSimilarity_ValueChanged(null, null);

            if (Win7TaskBarSupport.IsSupported)
            {
                win7TaskBar.SetProgressState(this.Handle, TaskbarProgressBarStatus.NoProgress);
            }

            // run garbage colection
            GC.Collect();
        }

        /// <summary>
        /// Handles "duplicates found" event reported by image manager.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void ImageManager_DuplicatesFound(object sender, ImgSearchEventArgs e)
        {
            DisplayResult(e.Result);
        }

        /// <summary>
        /// Callback function for loaded thumbnail image.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Thumbnail loader task item</param>
        private void FileManager_ThumbnailLoaded(object sender, ThumbLoaderItem e)
        {
            PictureBox pb = e.UserData as PictureBox;
            pb.Image = e.ThumbnailImage;
        }

        /// <summary>
        /// Handles "files added" event reported by file manager.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void FileManager_FilesAdded(object sender, EventArgs e)
        {
            trackBarSimilarity.Enabled = false;
            buttonPauseRestore.Visible = true;
            pluginsMenuItem.Enabled = false;

            // buttonPauseRestore.SetToggleState(false);
            msgBoxLoader.Text = String.Format(i18n_FilesAdded, fileManager.FileCount);
            msgBoxCalc.Text = String.Format("{0}...", i18n_CalculatingSim);

            if (Win7TaskBarSupport.IsSupported)
            {
                win7TaskBar.SetProgressState(this.Handle, TaskbarProgressBarStatus.Normal);
            }

#if DEBUG
            startTime = DateTime.Now;
#endif
        }

        /// <summary>
        /// Handles "file removed" event reported by file manager.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void FileManager_FileRemoved(object sender, FileEventArgs e)
        {
            RemoveFromDisplay(e.File);
        }

        /// <summary>
        /// Handler for clicking on plugin in menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void Plugin_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem m = (ToolStripMenuItem)sender;
            pluginManager.Plugins[m.Text].Enabled = m.Checked;
            PluginListChanged();
        }

        /// <summary>
        /// Handler for cliking on plugin options menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void PluginOptions_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem opt = (ToolStripMenuItem)sender;
            pluginManager.Plugins[opt.OwnerItem.Text].ShowOptionsDialog();
        }

        /// <summary>
        /// Handler for displaying image tooltip.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void Picturebox_MouseEnter(object sender, EventArgs e)
        {
            ThumbnailPictureBox pb = sender as ThumbnailPictureBox;
            ImageData img = pb.ImageData;
            thumbTooltip.RemoveAll();
            thumbTooltip.SetToolTip(
                pb,
                String.Format("{0}\n{1}: {2} x {3} px ; {4} KiB", img.File, i18n_Size, img.Size.Width, img.Size.Height, (img.FileSize / 1024)));
        }

        /// <summary>
        /// Handler for clicking on picturebox.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void Picturebox_MouseClick(object sender, MouseEventArgs e)
        {
            ThumbnailPictureBox p = sender as ThumbnailPictureBox;
            string file = p.ImageData.File;
            if (e.Button == MouseButtons.Right)
            {
                curFile = file;
            }
            else
            {
                if (!fileManager.SelectedFiles.Contains(file))
                {
                    fileManager.SelectedFiles.Add(file);
                    p.Selected = true;
                }
                else
                {
                    fileManager.SelectedFiles.Remove(file);
                    p.Selected = false;
                }
            }
        }

        /// <summary>
        /// Handler for moving track bar's slider.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void TrackBarSimilarity_ValueChanged(object sender, EventArgs e)
        {
            float limit = trackBarSimilarity.Value / 100.0f;
            float range = 0.1f;

            // show tooltip
            float max = 1 - limit + range;
            string tooltiptext = String.Empty;
            if (limit <= 0)
            {
                tooltiptext = i18n_Identical;
            }
            else
            {
                tooltiptext = String.Format("{0:0%} - {1:0%}", (max > 1) ? 1 : max, 1 - limit);
            }

            trackBarSimilarityToolTip.SetToolTip(trackBarSimilarity, tooltiptext);

            // find duplicate images
            imageManager.FindDups(limit, range);
        }

        /// <summary>
        /// Handler for clicking on "recalculate" button
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void ButtonRecalcualte_Click(object sender, EventArgs e)
        {
            bool paused = imageManager.PauseFileLoading();
            buttonRecalcualte.Visible = false;

            // set selected features
            int items = listBoxFeatures.CheckedItems.Count;
            imageManager.SetSelectedFeatures(listBoxFeatures.CheckedItems.Cast<string>());

            // recalculate distances
#if DEBUG
            startTime = DateTime.Now;
#endif
            imageManager.RecalculateSimilarity();
            if (paused)
            {
                imageManager.ResumeFileLoading();
            }
        }

        /// <summary>
        /// Handler for checking options in "Features" list box.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void ListboxFeatures_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            buttonRecalcualte.Visible = true;
        }

        /// <summary>
        /// Handler for clicking on "pause" button.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="toggled">new toggle state</param>
        private void ButtonPauseRestore_ToggleStateChanged(object sender, bool toggled)
        {
            if (toggled)
            {
                // pause
                imageManager.Pause();
                trackBarSimilarity.Enabled = true;
                TrackBarSimilarity_ValueChanged(null, null);

                if (Win7TaskBarSupport.IsSupported)
                {
                    win7TaskBar.SetProgressState(this.Handle, TaskbarProgressBarStatus.Paused);
                }
            }
            else
            {
                // resume
                trackBarSimilarity.Enabled = false;
                imageManager.Resume();
                
                if (Win7TaskBarSupport.IsSupported)
                {
                    win7TaskBar.SetProgressState(this.Handle, TaskbarProgressBarStatus.Normal);
                }
            }
        }

        /// <summary>
        /// Handles form load event.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void MainForm_Load(object sender, EventArgs e)
        {
            buttonRecalcualte.Visible = false;
            buttonPauseRestore.Visible = false;
        }

        /// <summary>
        /// Handles resizing of main panel.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void MainPanel_Resize(object sender, EventArgs e)
        {
            MainPanel.InvokeNestedLazyLoad();
        }

        #region Drag&Drop handlers
        /// <summary>
        /// Handles droping files onto form.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void MainForm_DragDrop(object sender, DragEventArgs e)
        {
            string[] dirs = (string[])e.Data.GetData(DataFormats.FileDrop);
            fileManager.AddFiles(dirs);
        }

        /// <summary>
        /// Handles draging files onto form.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void MainForm_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
            {
                e.Effect = DragDropEffects.All;
            }
        }
        #endregion

        #region menu click handlers

        /// <summary>
        /// Handles clicking on "clear images" in edit menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_ClearImages_Click(object sender, EventArgs e)
        {
            // clear files
            fileManager.Clear();

            // clear thumbnails cache
            pictureboxes.Clear();

            // clear display
            MainPanel.Controls.Clear();

            // set progress bars
            loaderProgressBar.Value = 0;
            calcProgressBar.Value = 0;

            // display image count
            DisplayImageCount();

            // set controlos
            trackBarSimilarity.Value = 0;
            buttonPauseRestore.Visible = false;
            buttonPauseRestore.SetToggleState(false);
            msgBoxLoader.Text = String.Empty;
            pluginsMenuItem.Enabled = true;

            // colect garbage
            GC.Collect();
        }

        /// <summary>
        /// Handles clicking on "remove from results" in menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_Remove_FromResults_Click(object sender, EventArgs e)
        {
            imageManager.RemoveResult(curFile);
            RemoveFromDisplay(curFile);
            fileManager.SelectedFiles.Remove(curFile);
            curFile = String.Empty;
        }

        /// <summary>
        /// Handles clicking on "remove from disk" in menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_Remove_FromDisk_Click(object sender, EventArgs e)
        {
            fileManager.RemoveFiles(new string[] { curFile });
            curFile = String.Empty;
        }

        /// <summary>
        /// Handles clicking on "remove selected from results" in menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_RemoveSelected_FromResults_Click(object sender, EventArgs e)
        {
            foreach (string file in fileManager.SelectedFiles)
            {
                imageManager.RemoveResult(file);
                ((ThumbnailPictureBox)pictureboxes[file]).Selected = false;
            }

            fileManager.SelectedFiles.Clear();
            TrackBarSimilarity_ValueChanged(null, null);
            curFile = String.Empty;
        }

        /// <summary>
        /// Handles clicking on "remove selected from disk" in menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_RemoveSelected_FromDisk_Click(object sender, EventArgs e)
        {
            fileManager.RemoveSelectedFiles();
            curFile = String.Empty;
        }

        /// <summary>
        /// Handles clicking on "select all" in edit menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_SelectAll_Click(object sender, EventArgs e)
        {
            foreach (Control panel in MainPanel.Controls)
            {
                if (MainPanel.ClientRectangle.IntersectsWith(panel.Bounds))
                {
                    foreach (Control pb in panel.Controls)
                    {
                        ThumbnailPictureBox thpb = pb as ThumbnailPictureBox;
                        if (thpb != null && !thpb.Selected && !fileManager.SelectedFiles.Contains(thpb.ImageData.File))
                        {
                            fileManager.SelectedFiles.Add(thpb.ImageData.File);
                            thpb.Selected = true;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Handles clicking on "open folder" in menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_OpenFolder_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("explorer.exe", "/select," + curFile);
        }

        /// <summary>
        /// Handles clicking on "open in default editor" in context menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_OpenInDefaultEditor_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start(curFile);
        }

        /// <summary>
        /// Handles clicking on "add files" button.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void FileMenuItem_AddFiles_Click(object sender, EventArgs e)
        {
            DialogResult dr = addFilesDialog.ShowDialog();
            if (dr != DialogResult.OK)
            {
                return;
            }

            fileManager.AddFiles(addFilesDialog.FileNames);
        }

        /// <summary>
        /// Handles clicking on "add folder" button.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void FileMenuItem_AddFolder_Click(object sender, EventArgs e)
        {
            DialogResult dr = addFolderDialog.ShowDialog();
            if (dr != DialogResult.OK)
            {
                return;
            }

            fileManager.AddFiles(new string[] { addFolderDialog.SelectedPath });
        }

        /// <summary>
        /// Handles click on "export results" button.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void FileMenuItem_ExportResults_Click(object sender, EventArgs e)
        {
            DialogResult dr = exportResultsDialog.ShowDialog();
            if (dr != DialogResult.OK)
            {
                return;
            }

            // get results
            List<SimilaritySearchResult> results = imageManager.ResultsToList();

            // sort list
            results.Sort(delegate(SimilaritySearchResult a, SimilaritySearchResult b)
                            {
                                return a.Similarity.CompareTo(b.Similarity);
                            });

            // save to file
            switch (exportResultsDialog.FilterIndex)
            {
                case 1:// export to cvs
                    StringBuilder sb = new StringBuilder();
                    string separator = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator;
                    foreach (SimilaritySearchResult ssr in results)
                    {
                        sb.AppendFormat("\"{0}\"", ssr.File1);
                        sb.Append(separator);
                        sb.AppendFormat("\"{0}\"", ssr.File2);
                        sb.Append(separator);
                        sb.AppendFormat("\"{0:0.00%}\"", 1 - ssr.Similarity);
                        sb.AppendLine();
                    }

                    using (StreamWriter sw =
                            new StreamWriter(exportResultsDialog.FileName, false, Encoding.UTF8))
                    {
                        sw.Write(sb.ToString());
                    }

                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Handles clicking on "exit" button.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void FileMenuItem_Exit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Handles clicking on "about" in help menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void HelpMenuItem_About_Click(object sender, EventArgs e)
        {
            new AboutBox().ShowDialog();
        }

        /// <summary>
        /// Handles clicking on "default" in plugins menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void PluginMenuItem_Default_Click(object sender, EventArgs e)
        {
            PluginListChanged();
        }

        #endregion

        #region handlers for showing menus
        /// <summary>
        /// Handles "export" enabled state in file menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void FileMenuItem_DropDownOpening(object sender, EventArgs e)
        {
            // "export result" menu item check
            fileMenuItem_ExportResults.Enabled = imageManager.Count > 0;
        }

        /// <summary>
        /// Handles "select all" enabled state in edit menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_DropDownOpening(object sender, EventArgs e)
        {
            // "select all"  menu item check
            editMenuItem_SelectAll.Enabled = MainPanel.HasChildren;

            // "reverse image search" menu item check
            editMenuItem_ReverseImageSearch.Enabled = imageManager.Count > 0;
        }

        /// <summary>
        /// Handles "remove All selected" enabled state in edit menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_RemoveSelected_DropDownOpening(object sender, EventArgs e)
        {
            // "remove all selected" items check
            ToolStripMenuItem parent = sender as ToolStripMenuItem;
            if (parent != null)
            {
                bool enabled = fileManager.SelectedFiles.Count != 0;
                foreach (ToolStripMenuItem item in parent.DropDownItems)
                {
                    item.Enabled = enabled;
                }
            }
        }

        /// <summary>
        /// Handles clicking on "Reverse image search" in edit menu.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event args</param>
        private void EditMenuItem_ReverseImageSearch_Click(object sender, EventArgs e)
        {
            addFilesDialog.Multiselect = false;
            DialogResult dr = addFilesDialog.ShowDialog();
            if (dr == DialogResult.OK)
            {
                ImageData idata = null;
                if (!imageManager.Files.TryGetValue(addFilesDialog.FileName, out idata))
                {
                    idata = new ImageData(addFilesDialog.FileName);
                    idata.CalculateFeatures(imageManager.FeaturesCollections);
                }

                Dictionary<ImageData, float> result = imageManager.GetSimilarity(idata);

                // display result
                MainPanel.SuspendLayout();
                MainPanel.Controls.Clear();

                Control[] panels = new Control[result.Count];
                int i = 0;
                var sorted = result.OrderByDescending(x => x.Value);
                foreach (KeyValuePair<ImageData, float> kvp in sorted)
                {
                    if (kvp.Key.File == idata.File)
                    {
                        continue;
                    }

                    LazyPanel imgPanel = new LazyPanel(kvp);
                    imgPanel.Height = 100;
                    imgPanel.AutoScroll = true;
                    imgPanel.Dock = DockStyle.Top;
                    imgPanel.LazyLoadCallback += new EventHandler(ReverseImageSearchResult_LazyLoadCallback);

                    panels[i++] = imgPanel;
                }

                MainPanel.Controls.AddRange(panels);
                MainPanel.ResumeLayout();
                MainPanel.InvokeNestedLazyLoad();
            }

            addFilesDialog.Multiselect = true;
        }

        #endregion

        /// <summary>
        /// Display reverse image search result using lazy loading.
        /// </summary>
        /// <param name="sender">sender object</param>
        /// <param name="e">event args</param>
        private void ReverseImageSearchResult_LazyLoadCallback(object sender, EventArgs e)
        {
            LazyPanel panel = sender as LazyPanel;
            KeyValuePair<ImageData, float> kvp = (KeyValuePair<ImageData, float>)panel.LazyData;

            PictureBox box = GetPictureBox(kvp.Key);
            box.Dock = DockStyle.Left;
            panel.Controls.Add(box);
            Label pct = new Label();
            pct.Dock = DockStyle.Left;
            pct.TextAlign = ContentAlignment.MiddleCenter;
            pct.Text = String.Format("{0:0.00%}", 1 - kvp.Value);
            panel.Controls.Add(pct);

            // invoke initial lazy load
            panel.InvokeNestedLazyLoad();
        }

        #endregion
        /// <summary>
        /// Initializes internationalized strings.
        /// </summary>
        private void InitializeI18n()
        {
            i18nManager = I18nManager.Manager;
            i18n_CalculatingSim = i18nManager.GetString("CALCULATING_SIM");
            i18n_FilesAdded = i18nManager.GetString("FILES_ADDED");
            i18n_ImageDone = i18nManager.GetString("IMG_DONE");
            i18n_Size = i18nManager.GetString("SIZE");
            i18n_Identical = i18nManager.GetString("IDENTICAL");

            buttonPauseRestore.ToggledText = i18nManager.GetString("MainForm_RESUME");
        }
    }
}
